import * as THREE from 'three';

import { TGALoader } from 'three/examples/jsm/loaders/TGALoader.js';

import { LoaderUtils } from './LoaderUtils.js';

function Loader(scene, object, SetObject, animations, SetAni) {
  const scope = this;

  this.texturePath = '';

  this.loadItemList = function (items) {
    LoaderUtils.getFilesFromItemList(items, function (files, filesMap) {
      scope.loadFiles(files, filesMap);
    });
  };

  this.loadFiles = function (files, filesMap) {
    if (files.length > 0) {
      filesMap = filesMap || LoaderUtils.createFilesMap(files);

      const manager = new THREE.LoadingManager();
      manager.setURLModifier(function (url) {
        url = url.replace(/^(\.?\/)/, ''); // remove './'

        const file = filesMap[url];

        if (file) {
          console.log('Loading', url);

          return URL.createObjectURL(file);
        }

        return url;
      });

      manager.addHandler(/\.tga$/i, new TGALoader());

      for (let i = 0; i < files.length; i++) {
        scope.loadFile(files[i], manager);
      }
    }
  };

  this.loadFile = function (file, manager) {
    const filename = file.name;
    const extension = filename.split('.').pop().toLowerCase();

    const reader = new FileReader();
    reader.addEventListener('progress', function (event) {
      const size = '(' + Math.floor(event.total / 1000) + ' KB)';
      const progress = Math.floor((event.loaded / event.total) * 100) + '%';

      console.log('Loading', filename, size, progress);
    });

    switch (extension) {
      case 'glb': {
        reader.addEventListener(
          'load',
          async function (event) {
            const contents = event.target.result;
            const { DRACOLoader } = await import('three/examples/jsm/loaders/DRACOLoader.js');
            const { GLTFLoader } = await import('three/examples/jsm/loaders/GLTFLoader.js');
            const dracoLoader = new DRACOLoader();
            dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
            const loader = new GLTFLoader();
            loader.setDRACOLoader(dracoLoader);
            loader.parse(
              contents,
              '',
              function (result) {
                const gltfScene = result.scene;
                gltfScene.name = filename;
                gltfScene.tag = 'obj';
                gltfScene.traverseVisible(child => {
                  if (child.type == 'Mesh') child.castShadow = true;
                });
                scene.add(gltfScene);

                SetAni([...animations, ...result.animations]);
                SetObject([...scene.children]);
              },
              event => {
                console.log(event);
              }
            );
          },
          false
        );
        reader.readAsArrayBuffer(file);

        break;
      }

      case 'gltf': {
        reader.addEventListener(
          'load',
          async function (event) {
            const contents = event.target.result;

            let loader;

            const { DRACOLoader } = await import('three/examples/jsm/loaders/DRACOLoader.js');
            const { GLTFLoader } = await import('three/examples/jsm/loaders/GLTFLoader.js');

            const dracoLoader = new DRACOLoader();

            dracoLoader.setDecoderPath('three/examples/js/libs/draco/gltf/');

            loader = new GLTFLoader(manager);
            loader.setDRACOLoader(dracoLoader);

            loader.parse(contents, '', function (result) {
              const gltfScene = result.scene;
              gltfScene.name = filename;

              gltfScene.tag = 'obj';
              scene.add(gltfScene);
              gltfScene.traverseVisible(child => {
                if (child.type == 'Mesh') child.castShadow = true;
              });
              SetAni([...animations, ...result.animations]);
              SetObject([...scene.children]);
            });
          },
          false
        );
        reader.readAsArrayBuffer(file);

        break;
      }

      case 'js':
      case 'json': {
        reader.addEventListener(
          'load',
          function (event) {
            const contents = event.target.result;

            // 2.0

            if (contents.indexOf('postMessage') !== -1) {
              const blob = new Blob([contents], { type: 'text/javascript' });
              const url = URL.createObjectURL(blob);

              const worker = new Worker(url);

              worker.onmessage = function (event) {
                event.data.metadata = { version: 2 };
                handleJSON(event.data);
              };

              worker.postMessage(Date.now());

              return;
            }

            // >= 3.0

            let data;

            try {
              data = JSON.parse(contents);
              console.log(data);
            } catch (error) {
              alert(error);
              return;
            }

            handleJSON(data);
          },
          false
        );
        reader.readAsText(file);

        break;
      }

      case 'obj': {
        reader.addEventListener(
          'load',
          async function (event) {
            const contents = event.target.result;

            const { OBJLoader } = await import('three/examples/jsm/loaders/OBJLoader.js');

            const object = new OBJLoader().parse(contents);
            object.name = filename;
            scene.add(object);
            //editor.execute(new AddObjectCommand(editor, object));
          },
          false
        );
        reader.readAsText(file);

        break;
      }
    }

    function handleJSON(data) {
      if (data.metadata === undefined) {
        // 2.0

        data.metadata = { type: 'Geometry' };
      }

      if (data.metadata.type === undefined) {
        // 3.0

        data.metadata.type = 'Geometry';
      }

      if (data.metadata.formatVersion !== undefined) {
        data.metadata.version = data.metadata.formatVersion;
      }

      switch (data.metadata.type.toLowerCase()) {
        case 'buffergeometry': {
          const loader = new THREE.BufferGeometryLoader();
          const result = loader.parse(data);

          const mesh = new THREE.Mesh(result);

          scene.add(mesh);
          SetObject([...object, mesh]);

          break;
        }

        case 'geometry':
          console.error('Loader: "Geometry" is no longer supported.');

          break;

        case 'object': {
          const loader = new THREE.ObjectLoader();
          loader.setResourcePath(scope.texturePath);

          loader.parse(data, function (result) {
            if (result.isScene) {
            } else {
              scene.add(result);
              SetObject([...object, result]);
            }
          });

          break;
        }

        case 'app':
          break;
      }
    }
  };
}
export { Loader };
